package org.ushio.common_module.debounce;

import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;
import org.ushio.common_module.config.security.JwtUtil;
import org.ushio.common_module.util.exception.DebounceException;

/**
 * @author xlj
 * @since 2024-10-1
 * @version 1.0
 */
@Slf4j
@Aspect
@Component
public class DebounceAspect {

    private static final String DEBOUNCE_KEY_PREFIX = "debounce:";
    private static final String ANONYMOUS_USER_PREFIX = "anonymous_user_unique_identifier_0123456789abcdef";

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private JwtUtil jwtUtil;

    /**
     * 定义切点，用于匹配所有被 {@link Debounce} 注解标记的方法。
     */
    @Pointcut("@annotation(Debounce)")
    public void debounceMethods() {}

    /**
     * 环绕通知，用于拦截带有 {@link Debounce} 注解的方法，
     * 并在这些方法执行前后添加防抖逻辑。
     *
     * @param joinPoint 切入点表示目标方法的调用点
     * @param debounce 防抖注解实例，包含防抖的时间间隔信息
     * @return 目标方法的返回值
     * @throws Throwable 如果发生异常，则抛出
     */
    @Around("debounceMethods() && @annotation(debounce)")
    public Object doDebounce(ProceedingJoinPoint joinPoint, Debounce debounce) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        // 从请求头中读取 token 并解析用户名
        String username = getUsernameFromToken(request);

        // 判断请求来源并输出调试信息
        if (username != null) {
            log.info("处理已认证用户 {} 的请求 ", username);
        } else {
            log.info("处理来自匿名用户的请求");
        }

        // 构建唯一的 Redis key
        String userIdentifier = username != null ? username : ANONYMOUS_USER_PREFIX;
        String key = DEBOUNCE_KEY_PREFIX + request.getMethod() + ":" + request.getRequestURI() + ":" + userIdentifier;

        if (Boolean.TRUE.equals(this.redisTemplate.hasKey(key))) {
            log.warn("请求过于频繁，请稍后再试");

            throw new DebounceException(429, "请求过于频繁，请稍后再试");
        } else {
            this.redisTemplate.opsForValue().set(key, "", debounce.interval(), TimeUnit.MILLISECONDS);
            return joinPoint.proceed();
        }
    }

    /**
     * 从 HTTP 请求头部的 Authorization 字段中提取 JWT 令牌，并尝试从中获取用户名。
     *
     * @param request HTTP 请求对象
     * @return 解析得到的用户名，如果解析失败或没有提供令牌，则返回null
     */
    private String getUsernameFromToken(HttpServletRequest request) {
        final String authorizationHeader = request.getHeader("Authorization");

        String username = null;
        String jwt;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            try {
                username = jwtUtil.getUsernameFromToken(jwt);
                log.info("令牌验证通过，从中提取的用户名: {}", username);
            } catch (Exception e) {
                log.error("令牌验证失败,未提取到用户名: {}", e.getMessage());
            }
        }
        return username;
    }
}